﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SDS.SPHealthAnalyzerRules.Delegation.Helpers.Win32API
{
    /// <summary>
    /// Helper functions for Windows Local Security Authority Subsystem Service
    /// </summary>
    public static class Lsa
    {
        /// <summary>
        /// Source: http://msdn.microsoft.com/en-us/library/cc232779.aspx
        /// </summary>
        public enum Authorizations
        {
            // reference: http://msdn.microsoft.com/en-us/library/aa375728.aspx

            #region Account Rights
            // reference: http://msdn.microsoft.com/en-us/library/bb545671.aspx
            
            /// <summary>
            /// Log on as a service
            /// </summary>
            [LsaPrivilege(InternalTextName = "SeServiceLogonRight", Description = "Log on as a service")]
            SE_SERVICE_LOGON_NAME,
            
            /// <summary>
            /// DENY log on as a service
            /// </summary>
            [LsaPrivilege(InternalTextName = "SeDenyServiceLogonRight", Description = "Deny log on as a service")]
            SE_DENY_SERVICE_LOGON_NAME,

	        #endregion

            #region Privileges
            // reference: http://msdn.microsoft.com/en-us/library/bb530716.aspx

            /// <summary>
            /// Impersonate a client after authetication
            /// </summary>
            [LsaPrivilege(InternalTextName = "SeImpersonatePrivilege", Description = "Impersonate a client after authentication")]
            SE_IMPERSONATE_NAME,

            /// <summary>
            /// Act as part of the operating system
            /// </summary>
            [LsaPrivilege(InternalTextName = "SeTcbPrivilege", Description = "Act as part of the operating system")]
            SE_TCB_NAME

	        #endregion

        }

        public static bool UserHasRights(string AccountName, Authorizations Authorization)
        {
            using (Willys.LsaSecurity.LsaWrapper lsa = new Willys.LsaSecurity.LsaWrapper())
            {
                var AuthorizedAccounts = lsa.GetAccounts(Authorizations_TextName(Authorization))
                                            .Where(a => !String.IsNullOrEmpty(a))                                                   // remove NULL (bad sid) accounts
                                            .Where(a => !a.StartsWith(@"NT AUTHORITY\"))                                            // remove NTAUTHORITY accounts
                                            .Where(a => !a.StartsWith(@"NT SERVICE\"))                                              // remove app pool "accounts"
                                            .Where(a => !a.StartsWith(@"IIS APPPOOL\"))                                             // remove app pool "accounts"
                                            .Select(a => a.Replace(@"BUILTIN\", String.Format(@"{0}\", Environment.MachineName)))   // rename BUILTIN to [COMPUTERNAME]
                                            .Select(a => a.ToLower());

                return AuthorizedAccounts.Contains(@"\Everyone") ||
                       AuthorizedAccounts.Contains(AccountName.ToLower()) ||
                       AuthorizedAccounts.Any(a => ADSI.AD.MemberOf(AccountName, a));
            }
        }

        #region Internal - Attributes
        private class LsaPrivilegeAttribute : Attribute
        {
            public string InternalTextName { get; set; }
            public string Description { get; set; }
        }
        #endregion

        private static string Authorizations_TextName(Authorizations Authorization)
        {
            // Get the type
            Type type = Authorization.GetType();
            // Get fieldinfo for this type
            System.Reflection.FieldInfo fieldInfo = type.GetField(Authorization.ToString());
            // Get the stringvalue attributes
            LsaPrivilegeAttribute[] attribs = fieldInfo.GetCustomAttributes(typeof(LsaPrivilegeAttribute), false) as LsaPrivilegeAttribute[];
            // Return the first if there was a match.
            return attribs.Length > 0 ? attribs[0].InternalTextName : null;
        }
    } // class Lsa
} // namespace


// Ref: http://bytes.com/topic/c-sharp/answers/266094-lsa-functions
//      from Willy Denoyette [MVP]
// Ref: http://stackoverflow.com/questions/2112901/i-successfully-called-advapi32s-lsaenumerateaccountrights-from-c-now-how-do
//      updated to use POLICY_FLAGS (least privileged access) and Marshal.PtrToStructure(hPrivileges to LSA_UNICODE_STRING[])
namespace Willys.LsaSecurity
{
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Management;
    using System.Runtime.CompilerServices;
    using System.ComponentModel;

    using LSA_HANDLE = IntPtr;

    #region Structs

    /// <summary>
    /// API documentation: http://msdn.microsoft.com/en-us/library/ms721829.aspx
    /// </summary>
    /// <remarks>
    /// API indicates that LsaOpenPolicy(..) no longer uses this information. Initializers added to simply use.
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    struct LSA_OBJECT_ATTRIBUTES
    {
        internal int Length;
        internal IntPtr RootDirectory;
        internal IntPtr ObjectName;
        internal int Attributes;
        internal IntPtr SecurityDescriptor;
        internal IntPtr SecurityQualityOfService;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct LSA_UNICODE_STRING
    {
        internal ushort Length;
        internal ushort MaximumLength;
        [MarshalAs(UnmanagedType.LPWStr)]
        internal string Buffer;
    }

	#endregion

    sealed class Win32Sec
    {
        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
         SuppressUnmanagedCodeSecurity]
        public static extern uint LsaOpenPolicy(LSA_UNICODE_STRING[] SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, int AccessMask, out IntPtr PolicyHandle);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError=true),
         SuppressUnmanagedCodeSecurity]
        public static extern uint LsaEnumerateAccountRights(LSA_HANDLE PolicyHandle, [MarshalAs(UnmanagedType.LPArray)] byte[] AccountSid, out IntPtr UserRights, out uint CountOfRights);

        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
         SuppressUnmanagedCodeSecurityAttribute]
        public static extern uint LsaEnumerateAccountsWithUserRight(LSA_HANDLE PolicyHandle, LSA_UNICODE_STRING[] UserRights, out IntPtr EnumerationBuffer, out uint CountReturned);

        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
         SuppressUnmanagedCodeSecurity]
        public static extern uint LsaAddAccountRights(LSA_HANDLE PolicyHandle, IntPtr pSID, LSA_UNICODE_STRING[] UserRights, int CountOfRights);

        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
         SuppressUnmanagedCodeSecurity]
        public static extern int LsaLookupNames2(LSA_HANDLE PolicyHandle, uint Flags, uint Count, LSA_UNICODE_STRING[] Names, ref IntPtr ReferencedDomains, ref IntPtr Sids);

        [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true),
         SuppressUnmanagedCodeSecurity]
        public static extern int LsaLookupSids(LSA_HANDLE PolicyHandle, uint Count, IntPtr Sids, out IntPtr ReferencedDomains, out IntPtr Names);
        // LsaLookupSids2 is for Win8
        //public static extern int LsaLookupSids2(LSA_HANDLE PolicyHandle, uint LookupOptions, uint Count, ref IntPtr Sids, ref IntPtr ReferencedDomains, ref IntPtr Names);

        [DllImport("advapi32")]
        public static extern int LsaNtStatusToWinError(int NTSTATUS);

        [DllImport("advapi32")]
        public static extern int LsaClose(IntPtr PolicyHandle);

        [DllImport("advapi32")]
        public static extern int LsaFreeMemory(IntPtr Buffer);

        [DllImport("advapi32.dll")]
        public static extern uint GetLengthSid(IntPtr pSid);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool ConvertStringSidToSid(string lbBuffer, out IntPtr ptrSid);

        [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool ConvertSidToStringSid([MarshalAs(UnmanagedType.LPArray)] byte[] pSID, out IntPtr ptrSid);

    }

    public sealed class LsaWrapper : IDisposable
    {
        #region Structs

        [StructLayout(LayoutKind.Sequential)]
        struct LSA_TRUST_INFORMATION
        {
            internal LSA_UNICODE_STRING Name;
            internal IntPtr Sid;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct LSA_TRANSLATED_SID2
        {
            internal SID_NAME_USE Use;
            internal IntPtr Sid;
            internal int DomainIndex;
            uint Flags;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct LSA_TRANSLATED_NAME
        {
            internal SID_NAME_USE Use;
            internal LSA_UNICODE_STRING Name;
            internal long DomainIndex;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct LSA_REFERENCED_DOMAIN_LIST
        {
            internal uint Entries;
            internal IntPtr Domains;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct LSA_ENUMERATION_INFORMATION
        {
            internal IntPtr PSid;
        }

        #endregion

        #region Enums

        enum SID_NAME_USE : int
        {
            User = 1,
            Group = 2,
            Domain = 3,
            Alias = 4,
            KnownGroup = 5,
            DeletedAccount = 6,
            Invalid = 7,
            Unknown = 8,
            Computer = 9
        }

        [Flags]
        enum Access : int
        {
            POLICY_READ = 0x20006,
            POLICY_ALL_ACCESS = 0x00F0FFF,
            POLICY_EXECUTE = 0x20801,
            POLICY_WRITE = 0x207F8,

            POLICY_VIEW_LOCAL_INFORMATION = 0x1,
            POLICY_LOOKUP_NAMES = 0x00000800
        }
 
        #endregion

        const uint STATUS_NO_MORE_ENTRIES = 0x8000001A;
        const uint STATUS_ACCESS_DENIED = 0xc0000022;
        const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a;
        const uint STATUS_NO_MEMORY = 0xc0000017;

        IntPtr lsaHandle = IntPtr.Zero;

        #region Constructors

        public LsaWrapper() : this(null) { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="systemName">
        /// local system if null
        /// </param>
        public LsaWrapper(string systemName)
        {
            LSA_OBJECT_ATTRIBUTES lsaAttr = LsaObjectAttributes_Default();

            // identify machine to query
            LSA_UNICODE_STRING[] system = null;
            if (systemName != null)
            {
                system = new LSA_UNICODE_STRING[1];
                system[0] = InitLsaString(systemName);
            }

            // connect to LSA (least privileges required)
            uint ret = Win32Sec.LsaOpenPolicy(system, ref lsaAttr, (int)(Access.POLICY_LOOKUP_NAMES | Access.POLICY_VIEW_LOCAL_INFORMATION), out lsaHandle);

            // success
            if (ret == 0)
                return;

            // failure
            if (ret == STATUS_ACCESS_DENIED)
                throw new UnauthorizedAccessException();
            if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
                throw new OutOfMemoryException();
            throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
        }

        private static LSA_OBJECT_ATTRIBUTES LsaObjectAttributes_Default()
        {
            LSA_OBJECT_ATTRIBUTES lsaAttr;
            lsaAttr.RootDirectory = IntPtr.Zero;
            lsaAttr.ObjectName = IntPtr.Zero;
            lsaAttr.Attributes = 0;
            lsaAttr.SecurityDescriptor = IntPtr.Zero;
            lsaAttr.SecurityQualityOfService = IntPtr.Zero;
            lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
            return lsaAttr;
        }

        #endregion

        #region Methods

        //public void AddPrivileges(string account, string privilege)
        //{
        //    IntPtr pSid = GetSIDInformation(account);
        //    LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
        //    privileges[0] = InitLsaString(privilege);
        //    uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, pSid, privileges, 1);

        //    // success
        //    if (ret == 0)
        //        return;

        //    // failure
        //    if (ret == STATUS_ACCESS_DENIED)
        //        throw new UnauthorizedAccessException();
        //    if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
        //        throw new OutOfMemoryException();
        //    throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
        //}

        public bool CheckPrivileges(string account, string privilege)
        {
            // LsaEnumerateAccountRights requires SID to be byte[]... attempts to use IntPtr FAILED.
            byte[] sid = LsaLookupNames2(account);

            // return variables
            LSA_HANDLE hPrivileges;
            uint privileges_count;

            uint ret = Win32Sec.LsaEnumerateAccountRights(lsaHandle, sid, out hPrivileges, out privileges_count);

            // success
            if (ret == 0)
            {
                // container to store results
                List<string> privileges = new List<string>();

                // marshall the pointer to the structure, add result into list
                LSA_UNICODE_STRING LSA_privilege = new LSA_UNICODE_STRING();
                for (ulong i = 0; i < privileges_count; i++)
                {
                    IntPtr itemAddr = new IntPtr(hPrivileges.ToInt64() + (long)(i * (ulong)Marshal.SizeOf(typeof(LSA_UNICODE_STRING))));
                    LSA_privilege = (LSA_UNICODE_STRING)Marshal.PtrToStructure(itemAddr, typeof(LSA_UNICODE_STRING));
                    privileges.Add(LSA_privilege.Buffer);
                }

                // check the results of the structure
                bool outval = privileges.Contains(privilege);

                // free the memory
                Win32Sec.LsaFreeMemory(hPrivileges);

                // return results
                return outval;
            }

            // failure
            if (ret == STATUS_ACCESS_DENIED)
                throw new UnauthorizedAccessException();
            if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
                throw new OutOfMemoryException();
            throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
        }

        public IEnumerable<string> GetAccounts(string privilege)
        {
            IntPtr buffer = IntPtr.Zero;

            try
            {
                // input parameters
                LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[] { InitLsaString(privilege) };

                // output parameters
                uint count = 0;
                uint ret = Win32Sec.LsaEnumerateAccountsWithUserRight(lsaHandle, privileges, out buffer, out count);

                if (ret == STATUS_NO_MORE_ENTRIES)
                    return new List<string>();

                // exceptions
                if (ret == STATUS_ACCESS_DENIED)
                    throw new UnauthorizedAccessException();
                if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
                    throw new OutOfMemoryException();
                if (ret != 0)
                    throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));

                // prepare return data
                List<string> outval = new List<string>();

                // process and marshal data into return object
                for (ulong i = 0; i < count; i++)
                {
                    IntPtr itemAddr = new IntPtr(buffer.ToInt64() + (long)(i * (ulong)Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION))));
                    LSA_ENUMERATION_INFORMATION LsaInfo = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure(itemAddr, typeof(LSA_ENUMERATION_INFORMATION));

                    // convert sid from PSID to byte[]
                    int sidLen = (int)Win32Sec.GetLengthSid(LsaInfo.PSid);
                    byte[] sid = new byte[sidLen];
                    Marshal.Copy(LsaInfo.PSid, sid, 0, sidLen);

                    outval.Add(LsaLookupSids(sid));
                }

                return outval;
            }
            finally
            {
                if (buffer != IntPtr.Zero)
                    Win32Sec.LsaFreeMemory(buffer);
            }
        }

        #endregion

        #region Destructor / Dispose

        public void Dispose()
        {
            if (lsaHandle != IntPtr.Zero)
            {
                Win32Sec.LsaClose(lsaHandle);
                lsaHandle = IntPtr.Zero;
            }
            GC.SuppressFinalize(this);
        }
        ~LsaWrapper()
        {
            Dispose();
        }

        #endregion

        #region Helper Functions

        byte[] LsaLookupNames2(string account)
        {
            LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1];
            LSA_TRANSLATED_SID2 lts;
            IntPtr tsids = IntPtr.Zero;
            IntPtr tdom = IntPtr.Zero;
            names[0] = InitLsaString(account);
            lts.Sid = IntPtr.Zero;
            System.Diagnostics.Debug.WriteLine(String.Format("String account: {0}", names[0].Length));
            int ret = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids);
            if (ret != 0)
                throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret));
            lts = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(tsids, typeof(LSA_TRANSLATED_SID2));

            int sidLen = (int)Win32Sec.GetLengthSid(lts.Sid);
            byte[] sid = new byte[sidLen];
            Marshal.Copy(lts.Sid, sid, 0, sidLen);

            Win32Sec.LsaFreeMemory(tsids);
            Win32Sec.LsaFreeMemory(tdom);
            return sid;
        }

        string LsaLookupSids(byte[] sid)
        {
            IntPtr pLsaInfo = IntPtr.Zero;
            // set some out vals
            IntPtr pNames = IntPtr.Zero;
            IntPtr pDomain = IntPtr.Zero;

            try
            {
                pLsaInfo = ConvertSid_ByteArray_To_PSID(sid);

                int ret = Win32Sec.LsaLookupSids(lsaHandle, 1, pLsaInfo, out pDomain, out pNames);
                if (ret != 0)
                {
                    var Win32Err = Win32Sec.LsaNtStatusToWinError(ret);

                    // handle this exception (unable to map sid)
                    if (Win32Err == 1332)
                        return null;

                    throw new Win32Exception(Win32Err);
                }

                // Since this function only ever provides a single SID to the call, I don't need to worry about iterating over all returned domains/accounts
                var domainLst = (LSA_REFERENCED_DOMAIN_LIST)Marshal.PtrToStructure(pDomain, typeof(LSA_REFERENCED_DOMAIN_LIST));
                var domainInfo = (LSA_TRUST_INFORMATION)Marshal.PtrToStructure(domainLst.Domains, typeof(LSA_TRUST_INFORMATION));
                var LsaTranslatedName = ((LSA_TRANSLATED_NAME)Marshal.PtrToStructure(pNames, typeof(LSA_TRANSLATED_NAME)));

                // if dealing with more than one LSA_TRUST_INFORMATION
                //var domainInfos = new LSA_TRUST_INFORMATION[domainLst.Entries];

                string domain = domainInfo.Name.Buffer;
                string name = LsaTranslatedName.Name.Buffer.Substring(0, LsaTranslatedName.Name.Length / 2);

                return String.Format(@"{0}\{1}", domain, name);
            }
            finally
            {
                Win32Sec.LsaFreeMemory(pLsaInfo);
                Win32Sec.LsaFreeMemory(pDomain);
                Win32Sec.LsaFreeMemory(pNames);
            }
        } // LsaLookupSids(...)

        private static IntPtr ConvertSid_ByteArray_To_PSID(byte[] sid)
        {

            // convert byte array to string, via pString
            IntPtr pstrSID;
            Win32Sec.ConvertSidToStringSid(sid, out pstrSID);
            string strSID = Marshal.PtrToStringAuto(pstrSID);

            // convert string pointer to PSID
            IntPtr psid = IntPtr.Zero;
            Win32Sec.ConvertStringSidToSid(strSID, out psid);

            // create LSAInfo struct and pointer, then marshal the structure
            LSA_ENUMERATION_INFORMATION lsaInfo = new LSA_ENUMERATION_INFORMATION { PSid = psid };
            IntPtr pLsaInfo = Marshal.AllocHGlobal(Marshal.SizeOf(lsaInfo));
            Marshal.StructureToPtr(lsaInfo, pLsaInfo, true);
            return pLsaInfo;
        }
        
        static LSA_UNICODE_STRING InitLsaString(string s)
        {
            // Unicode strings max. 32KB
            if (s.Length > 0x7ffe)
                throw new ArgumentException("String too long");

            LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
            lus.Buffer = s;
            //removed due to unsafe context errors when using sizeof()
            // lus.Length = (ushort)(s.Length * sizeof(char));
            // lus.MaximumLength = (ushort)(lus.Length + sizeof(char));
            lus.Length = (ushort)(s.Length * 2); // Unicode char is 2 bytes
            lus.MaximumLength = (ushort)(lus.Length + 2);

            return lus;
        }

        #endregion

    }

}
